Skip to content

feat: typed assess errors + X-Quota headers + signer_match types#26

Merged
vvillait88 merged 10 commits intomainfrom
monetization-branding-rollout
May 2, 2026
Merged

feat: typed assess errors + X-Quota headers + signer_match types#26
vvillait88 merged 10 commits intomainfrom
monetization-branding-rollout

Conversation

@vvillait88
Copy link
Copy Markdown
Contributor

Summary

  • Add typed AssessError taxonomy on assess responses (paymentRequired, quotaExceeded, identityRequired, signerMismatch, etc.) so consumers can branch on denial codes without parsing strings
  • Capture X-Quota-Limit / X-Quota-Used / X-Quota-Reset response headers on AssessResponse for merchant-side observability
  • Add ResolveSigner request type + SignerMatch response type so SDK consumers can opt into server-side signer matching on /v1/assess (TEC-263 SDK side)
  • Add telemetrySignerMatch() method for posting gate-side outcomes to the telemetry endpoint
  • Tighten parseQuotaNumber to strict-integer parity with python-sdk
  • Drop "Paid-tier merchants" qualifier from associateWallet docstring
  • Bump version 2.1.1 → 2.2.0 (additive minor)

Closes TEC-274.

Test plan

  • Unit tests pass locally (bun run test)
  • Coverage above 95/90/95/95 thresholds
  • Lefthook pre-push (typecheck) passes
  • CI green on this PR
  • bun run build produces clean tsup output

🤖 Generated with Claude Code

vvillait88 and others added 10 commits May 1, 2026 06:06
…ring

The API itself gates the endpoint by tier; the SDK doesn't need to remind callers
about tier structure in a docstring. Consistent with the platform-wide pass to
keep tier/pricing language out of public-package source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rMatch

- New error subclasses (all subclass AgentScoreError so existing catches still work):
  PaymentRequiredError (402), TokenExpiredError (401 token_expired with parsed body
  fields exposed: verifyUrl/sessionId/pollSecret/pollUrl/nextSteps/agentMemory),
  InvalidCredentialError (401 invalid_credential), QuotaExceededError (429 quota_exceeded),
  RateLimitedError (429 rate_limited), TimeoutError (AbortError / request timeout)
- AssessResponse gains optional quota field { limit, used, reset } captured from
  X-Quota-Limit / X-Quota-Used / X-Quota-Reset response headers; agents can monitor
  approach-to-cap proactively (warn at 80%, alert at 95%) before hitting 429
- New telemetrySignerMatch(payload) method — fire-and-forget POST to
  /v1/telemetry/signer-match (commerce gate currently does this raw; will switch)
- Internal: request<T>() splits into request<T>() + requestWithHeaders<T>(); shared
  buildErrorFromResponse helper does status+code routing once

Backward-compatible: all new error classes inherit AgentScoreError; existing
catch (err: AgentScoreError) blocks unaffected. New quota field is optional.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emetrySignerMatch

- README: new "Typed error classes" subsection with class table + recovery example;
  "Quota observability" section showing how to read AssessResponse.quota; "Telemetry"
  section noting the new fire-and-forget method.
- CLAUDE.md: telemetrySignerMatch added to Methods; new "Errors + observability"
  section enumerating all 6 typed error subclasses + the quota field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second-pass review caught: Number('') is 0 (truthy, finite) while Python int('')
raises ValueError. Same for decimals — Number('1.5') is 1.5; int('1.5') raises.
A misconfigured proxy or test sending malformed X-Quota-* headers would surface
0 / 1.5 in node but null in python.

Replace `Number(raw)` + `isFinite` with regex `^-?\\d+$` test, matching int()'s
strict integer-only behavior. Empty strings, decimals, scientific notation, and
non-numeric strings now all return null in both SDKs.

Test added for empty + decimal headers (returns null) — parity with python-sdk's
existing test_extract_quota_falls_back_to_none_for_malformed_numeric_headers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures TEC-274 minor: new public exports (PaymentRequiredError,
TokenExpiredError, InvalidCredentialError, QuotaExceededError, RateLimitedError,
TimeoutError) + AssessResponse.quota field + telemetrySignerMatch method.

Skips intermediate 2.1.2 (docstring-only commit on this branch was unreleased).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-existing line, not introduced by TEC-274 work, but caught while sweeping
node-sdk tests for tier-language under feedback_no_internal_disclosure_in_public.
Tests are part of the public-package surface; "(free tier)" leaks our tier
structure. Replaced with the actual error semantics ("endpoint not enabled").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-review (parallel agents) found three test gaps + one misleading comment:

- parseQuotaNumber comment claimed Python int() doesn't trim whitespace / reject
  decimals — both wrong; Python int() does both. Reworded to describe Number()
  + parseInt() pitfalls (the actual reason for the regex), not Python diff.

Added tests:
- 429 → 200 retry path captures quota headers from the RETRY response (not the
  discarded original 429). Regression guard for requestWithHeaders retry logic.
- Generic 4xx fallthrough — 400 invalid_request and 403 account_cancelled both
  fall through to generic AgentScoreError (not any typed subclass). Verifies
  the discriminator only fires for the documented status+code pairs.
- TokenExpiredError with empty body — all parsed-body fields stay undefined,
  instance is still TokenExpiredError (not generic AgentScoreError fallthrough).
- TokenExpiredError with wrong-typed body fields — the typeof string check
  silently ignores number/array values; raw values still flow through `details`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lemetry/signer-match

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h assess

Add ResolveSigner request type and SignerMatch response type matching the
API's new server-side wallet-signer-match. assess() and the AssessOptions
shape gain an optional resolveSigner field; AssessResponse gains optional
signer_match. Drop vestigial on_the_fly + updated_at fields from
AssessResponse — they were never read by any caller.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88 vvillait88 merged commit d2648fb into main May 2, 2026
6 checks passed
@vvillait88 vvillait88 deleted the monetization-branding-rollout branch May 2, 2026 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant